package com.fishbone.secure.config;

import com.fishbone.jwt.JwtTokenGenerator;
import com.fishbone.jwt.JwtTokenStorage;
import com.fishbone.secure.converter.JwtGrantedAuthoritiesConverter;
import com.fishbone.secure.handler.FishBoneAccessDeniedHandler;
import com.fishbone.secure.handler.FishBoneAuthenticationEntryPoint;
import com.fishbone.secure.entity.WhiteName;
import com.fishbone.secure.interceptor.FishBoneUserTokenFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.core.convert.converter.Converter;
import org.springframework.http.HttpHeaders;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.util.CollectionUtils;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import javax.annotation.Resource;

import java.util.ArrayList;
import java.util.List;

/**
 * @author huyaxi
 */
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(WebSecurityConfigurerAdapter.class)
@ConditionalOnMissingBean(WebSecurityConfigurerAdapter.class)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
public class FishBoneWebSecurityConfiguration {


    @Configuration(proxyBeanMethods = false)
    @Order(SecurityProperties.BASIC_AUTH_ORDER)
    static class SecurityConfiguration extends WebSecurityConfigurerAdapter {
        private static final String ALLOWED_HEADERS = "*";
        private static final String ALLOWED_METHODS = "*";
        private static final String ALLOWED_ORIGIN = "*";

        @Resource
        private JwtTokenGenerator jwtTokenGenerator;

        @Resource
        private JwtTokenStorage jwtTokenStorage;

        @Resource
        private JwtDecoder jwtDecoder;

        @Resource
        private FilterInvocationSecurityMetadataSource filterInvocationSecurityMetadataSource;

        @Resource
        private AccessDecisionManager accessDecisionManager;

        @Resource
        private FishBoneSecureProperties secureProperties;

        @Override
        public void configure(HttpSecurity http) throws Exception {
            // http 基础配置
            http.httpBasic().and().csrf().disable();
            // http 跨域配置
            http.cors().configurationSource(corsConfigurationSource());
            http.authorizeRequests(authorizeRequests -> authorizeRequests
                    .antMatchers(passResources()).permitAll()
                    .anyRequest().authenticated()
                    .withObjectPostProcessor(filterSecurityInterceptorObjectPostProcessor()))
                    .oauth2ResourceServer(oauth2ResourceServer -> oauth2ResourceServer
                            .authenticationEntryPoint(new FishBoneAuthenticationEntryPoint())
                            .accessDeniedHandler(new FishBoneAccessDeniedHandler())
                            .jwt(jwtConfigurer -> jwtConfigurer
                                    .jwtAuthenticationConverter(jwtAuthenticationConverter())
                                    .decoder(jwtDecoder)));
            // 配置Token验证过滤器
            //http.addFilterAfter(new FishBoneUserTokenFilter(jwtTokenGenerator,jwtTokenStorage), UsernamePasswordAuthenticationFilter.class);
        }


        /**
         * 手动提取权限
         * @return
         */
        private Converter<Jwt, AbstractAuthenticationToken> jwtAuthenticationConverter() {
            JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
            JwtAuthenticationConverter authenticationConverter = new JwtAuthenticationConverter();
            authenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter);
            return authenticationConverter;
        }

        /**
         * 自定义 FilterSecurityInterceptor  ObjectPostProcessor 以替换默认配置达到动态权限的目的
         *
         * @return ObjectPostProcessor
         */
        private ObjectPostProcessor<FilterSecurityInterceptor> filterSecurityInterceptorObjectPostProcessor() {
            return new ObjectPostProcessor<FilterSecurityInterceptor>() {
                @Override
                public <O extends FilterSecurityInterceptor> O postProcess(O object) {
                    object.setAccessDecisionManager(accessDecisionManager);
                    object.setSecurityMetadataSource(filterInvocationSecurityMetadataSource);
                    return object;
                }
            };
        }


        /**
         * 跨域配置
         * @return
         */
        private CorsConfigurationSource corsConfigurationSource() {
            CorsConfiguration configuration = new CorsConfiguration();
            configuration.addAllowedHeader(ALLOWED_HEADERS);
            configuration.addAllowedMethod(ALLOWED_METHODS);
            configuration.addAllowedOrigin(ALLOWED_ORIGIN);
            // 暴露哪些头部信息（因为跨域访问默认不能获取全部头部信息）
            configuration.addExposedHeader(HttpHeaders.ACCEPT);
            configuration.setAllowCredentials(true);
            configuration.setMaxAge(18000L);
            UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
            source.registerCorsConfiguration("/**", configuration);
            return source;
        }


        /**
         * 资源白名单
         *
         * @return
         */
        private String[] passResources() {
            List<WhiteName> whiteNames = secureProperties.getWhiteNames();
            if(CollectionUtils.isEmpty(whiteNames)){
                return new String[]{};
            }
            List<String> resources = new ArrayList<>();
            whiteNames.forEach(whiteName -> resources.add(whiteName.getUri()));
            String[] paths = new String[resources.size()];
            return resources.toArray(paths);
        }
    }
}
